/*
 * Copyright 2015 The AppAuth for Android Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.openid.appauthdemo;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.snackbar.Snackbar;

import net.openid.appauth.AppAuthConfiguration;
import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationResponse;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceConfiguration;
import net.openid.appauth.AuthorizationServiceDiscovery;
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.EndSessionRequest;
import net.openid.appauth.TokenRequest;
import net.openid.appauth.TokenResponse;
import okio.Okio;
import org.joda.time.format.DateTimeFormat;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;


/**
 * 显示已授权用户状态的 Activity。该活动接收授权流程的结果，必要时执行授权码交换
 * 以完成授权；在授权完成后，提供额外的操作（例如获取用户信息、刷新访问令牌）。
 */
public class TokenActivity extends AppCompatActivity {
    // 日志标签
    private static final String TAG = "TokenActivity";

    // 保存/恢复用户信息到 Activity 状态的键
    private static final String KEY_USER_INFO = "userInfo";

    // AuthorizationService 客户端，用于执行 token 请求等
    private AuthorizationService mAuthService;
    // 应用内的 AuthState 管理器单例
    private AuthStateManager mStateManager;
    // 原子引用保存最新的 userinfo JSON
    private final AtomicReference<JSONObject> mUserInfoJson = new AtomicReference<>();
    // 后台线程池，用于网络请求
    private ExecutorService mExecutor;
    // 应用配置（从 res/raw/auth_config.json 读取）
    private Configuration mConfiguration;

    private ActivityResultLauncher<Intent> endSessionLauncher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 初始化状态管理器、线程池和配置
        mStateManager = AuthStateManager.getInstance(this);
        mExecutor = Executors.newSingleThreadExecutor();
        mConfiguration = Configuration.getInstance(this);

        // 检查配置是否变更，若变更则清除会话并退出
        Configuration config = Configuration.getInstance(this);
        if (config.hasConfigurationChanged()) {
            Toast.makeText(
                    this,
                    "Configuration change detected",
                    Toast.LENGTH_SHORT)
                    .show();
            signOut();
            return;
        }

        // 构造 AuthorizationService，传入连接构建器并根据配置决定是否跳过 issuer https 校验
        mAuthService = new AuthorizationService(
                this,
                new AppAuthConfiguration.Builder()
                        .setConnectionBuilder(config.getConnectionBuilder())
                        // 如果配置不要求 HTTPS，则允许本地 HTTP issuer（仅限测试）
                        .setSkipIssuerHttpsCheck(!config.isHttpsRequired())
                        .build());

        setContentView(R.layout.activity_token);
        displayLoading("Restoring state...");

        // 恢复之前保存的 user info（如果存在）
        if (savedInstanceState != null) {
            try {
                mUserInfoJson.set(new JSONObject(savedInstanceState.getString(KEY_USER_INFO)));
            } catch (JSONException ex) {
                Log.e(TAG, "解析保存的 user info JSON 失败，已丢弃", ex);
            }
        }

        endSessionLauncher = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        signOut();
                        finish();
                    } else {
                        displayEndSessionCancelled();
                    }
                });
    }

    @Override
    protected void onStart() {
        super.onStart();

        // 如果线程池已关闭则重建
        if (mExecutor.isShutdown()) {
            mExecutor = Executors.newSingleThreadExecutor();
        }

        // 如果已有授权则展示授权界面
        if (mStateManager.getCurrent().isAuthorized()) {
            displayAuthorized();
            return;
        }

        // 存储的 AuthState 可能不完整；检查当前是否为从浏览器返回的授权结果
        AuthorizationResponse response = AuthorizationResponse.fromIntent(getIntent());
        AuthorizationException ex = AuthorizationException.fromIntent(getIntent());

        // 输出 Intent extras 以便调试（常用于查看重定向 URI 或错误信息）
        logIntentExtras(getIntent());

        if (response != null || ex != null) {
            Log.d(TAG, "收到授权流程结果: response=" + response + " ex=" + ex);
            mStateManager.updateAfterAuthorization(response, ex);
        }

        if (response != null && response.authorizationCode != null) {
            // 需要进行授权码交换
            Log.i(TAG, "授权响应包含 code，开始 token 交换。");
            mStateManager.updateAfterAuthorization(response, ex);
            exchangeAuthorizationCode(response);
        } else if (ex != null) {
            // 构造详细日志信息以便调试并记录完整堆栈
            String detailed = "Authorization flow failed: " + safeGetMessage(ex)
                    + "\nerror=" + ex.error
                    + "\nerrorDescription=" + ex.errorDescription
                    + "\nerrorUri=" + ex.errorUri
                    + "\ncode=" + ex.code;
            Log.e(TAG, detailed, ex);
            displayNotAuthorized(detailed);
        } else {
            Log.d(TAG, "没有保留的授权状态 - 需要重新授权");
            displayNotAuthorized("No authorization state retained - reauthorization required");
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle state) {
        super.onSaveInstanceState(state);
        // 保存用户信息以便在 Activity 重建（例如旋转）时恢复
        if (mUserInfoJson.get() != null) {
            state.putString(KEY_USER_INFO, mUserInfoJson.toString());
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 释放 AuthorizationService 和线程池
        mAuthService.dispose();
        mExecutor.shutdownNow();
    }

    @MainThread
    // 显示未授权的界面并提供重新授权选项
    private void displayNotAuthorized(String explanation) {
        findViewById(R.id.not_authorized).setVisibility(View.VISIBLE);
        findViewById(R.id.authorized).setVisibility(View.GONE);
        findViewById(R.id.loading_container).setVisibility(View.GONE);

        ((TextView)findViewById(R.id.explanation)).setText(explanation);
        findViewById(R.id.reauth).setOnClickListener((View view) -> signOut());
    }

    @MainThread
    // 显示加载中界面并设置描述文字
    private void displayLoading(String message) {
        findViewById(R.id.loading_container).setVisibility(View.VISIBLE);
        findViewById(R.id.authorized).setVisibility(View.GONE);
        findViewById(R.id.not_authorized).setVisibility(View.GONE);

        ((TextView)findViewById(R.id.loading_description)).setText(message);
    }

    @MainThread
    // 展示已授权状态并渲染相关 UI（包括用户信息、刷新 token 按钮等）
    private void displayAuthorized() {
        findViewById(R.id.authorized).setVisibility(View.VISIBLE);
        findViewById(R.id.not_authorized).setVisibility(View.GONE);
        findViewById(R.id.loading_container).setVisibility(View.GONE);

        AuthState state = mStateManager.getCurrent();

        TextView refreshTokenInfoView = findViewById(R.id.refresh_token_info);
        refreshTokenInfoView.setText((state.getRefreshToken() == null)
                ? R.string.no_refresh_token_returned
                : R.string.refresh_token_returned);

        TextView idTokenInfoView = (TextView) findViewById(R.id.id_token_info);
        idTokenInfoView.setText((state.getIdToken()) == null
                ? R.string.no_id_token_returned
                : R.string.id_token_returned);

        TextView accessTokenInfoView = (TextView) findViewById(R.id.access_token_info);
        if (state.getAccessToken() == null) {
            accessTokenInfoView.setText(R.string.no_access_token_returned);
        } else {
            Long expiresAt = state.getAccessTokenExpirationTime();
            if (expiresAt == null) {
                accessTokenInfoView.setText(R.string.no_access_token_expiry);
            } else if (expiresAt < System.currentTimeMillis()) {
                accessTokenInfoView.setText(R.string.access_token_expired);
            } else {
                String template = getResources().getString(R.string.access_token_expires_at);
                accessTokenInfoView.setText(String.format(template,
                        DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss ZZ").print(expiresAt)));
            }
        }

        Button refreshTokenButton = (Button) findViewById(R.id.refresh_token);
        refreshTokenButton.setVisibility(state.getRefreshToken() != null
                ? View.VISIBLE
                : View.GONE);
        refreshTokenButton.setOnClickListener((View view) -> refreshAccessToken());

        Button viewProfileButton = (Button) findViewById(R.id.view_profile);

        AuthorizationServiceDiscovery discoveryDoc =
                state.getAuthorizationServiceConfiguration().discoveryDoc;
        if ((discoveryDoc == null || discoveryDoc.getUserinfoEndpoint() == null)
                && mConfiguration.getUserInfoEndpointUri() == null) {
            viewProfileButton.setVisibility(View.GONE);
        } else {
            viewProfileButton.setVisibility(View.VISIBLE);
            viewProfileButton.setOnClickListener((View view) -> fetchUserInfo());
        }

        findViewById(R.id.sign_out).setOnClickListener((View view) -> endSession());

        View userInfoCard = findViewById(R.id.userinfo_card);
        JSONObject userInfo = mUserInfoJson.get();
        if (userInfo == null) {
            userInfoCard.setVisibility(View.INVISIBLE);
        } else {
            try {
                String name = "???";
                // 或者包含ClaimTypes.Name
                String ClaimTypes_Name = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
                if (userInfo.has("name")) {
                    name = userInfo.optString("name", name);
                } else if (userInfo.has(ClaimTypes_Name)) {
                    name = userInfo.optString(ClaimTypes_Name, name);
                }
                ((TextView) findViewById(R.id.userinfo_name)).setText(name);

                if (userInfo.has("portrait")) {
                    // 拼接图片地址，config中的host+portrait
                    // Determine a suitable host/base URL to resolve the portrait path.
                    String host = null;
                    // Configuration exposes a discovery Uri (or null). Use its string form if present.
                    Uri cfgDiscovery = mConfiguration.getDiscoveryUri();
                    if (cfgDiscovery != null) {
                        if (cfgDiscovery.getScheme() != null && cfgDiscovery.getAuthority() != null) {
                            host = cfgDiscovery.getScheme() + "://" + cfgDiscovery.getAuthority();
                        } else {
                            host = cfgDiscovery.toString();
                        }
                    } else if (discoveryDoc != null && discoveryDoc.getIssuer() != null) {
                        host = discoveryDoc.getIssuer().toString();
                    } else if (discoveryDoc != null && discoveryDoc.getUserinfoEndpoint() != null) {
                        // Fall back to scheme://authority from the userinfo endpoint
                        Uri ui = discoveryDoc.getUserinfoEndpoint();
                        if (ui.getScheme() != null && ui.getAuthority() != null) {
                            host = ui.getScheme() + "://" + ui.getAuthority();
                        }
                    }

                    if (host == null) {
                        Log.w(TAG, "No host available to build portrait URL; skipping portrait load");
                    } else {
                        String portraitPath = userInfo.optString("portrait", "").trim();
                        if (portraitPath.isEmpty()) {
                            Log.w(TAG, "portrait claim is empty; skipping portrait load");
                        } else {
                            // If the portrait value is already an absolute URL (http/https or data URI), use it as-is.
                            String portraitUrl;
                            if (portraitPath.startsWith("http://") || portraitPath.startsWith("https://")
                                    || portraitPath.startsWith("data:")) {
                                portraitUrl = portraitPath;
                            } else {
                                if (!portraitPath.startsWith("/")) {
                                    portraitPath = "/" + portraitPath;
                                }
                                portraitUrl = host + portraitPath;
                            }
                            // Fix JSON key typo and store the resolved absolute URL
                            userInfo.put("portrait", portraitUrl);
                            Log.d(TAG, "Loading portrait from: " + portraitUrl);

                            // 使用Glide加载图片
                            GlideApp.with(TokenActivity.this)
                                    .load(Uri.parse(portraitUrl))
                                    .fitCenter()
                                    .into((ImageView) findViewById(R.id.userinfo_profile));
                        }
                    }
                }

                ((TextView) findViewById(R.id.userinfo_json)).setText(mUserInfoJson.toString());
                userInfoCard.setVisibility(View.VISIBLE);
            } catch (JSONException ex) {
                Log.e(TAG, "读取 userinfo JSON 失败", ex);
            }
        }
    }

    @MainThread
    // 使用刷新令牌更新访问令牌
    private void refreshAccessToken() {
        displayLoading("Refreshing access token");
        performTokenRequest(
                mStateManager.getCurrent().createTokenRefreshRequest(),
                this::handleAccessTokenResponse);
    }

    @MainThread
    // 显示加载中提示并执行授权码交换
    private void exchangeAuthorizationCode(AuthorizationResponse authorizationResponse) {
        displayLoading("Exchanging authorization code");
        performTokenRequest(
                authorizationResponse.createTokenExchangeRequest(),
                this::handleCodeExchangeResponse);
    }

    @MainThread
    // 执行 token 请求（用于 code 交换或 refresh token），并处理某些非标准服务器需要当前 access_token 的情况
    private void performTokenRequest(
            TokenRequest request,
            AuthorizationService.TokenResponseCallback callback) {
        // 记录请求信息以便调试
        try {
            Log.i(TAG, "performTokenRequest: request=" + (request == null ? "null" : request.jsonSerializeString()));
        } catch (Exception e) {
            Log.i(TAG, "performTokenRequest: 无法序列化请求", e);
        }
        // 在 UI 上短暂提示
        runOnUiThread(() -> showSnackbar("Starting token exchange"));

        ClientAuthentication clientAuthentication;
        try {
            clientAuthentication = mStateManager.getCurrent().getClientAuthentication();
        } catch (ClientAuthentication.UnsupportedAuthenticationMethod ex) {
            Log.d(TAG, "无法构造客户端认证方法，token 请求无法执行", ex);
            displayNotAuthorized("Client authentication method is unsupported");
            return;
        }

        try {
            // 处理某些服务器需要把当前 access_token 作为额外参数传入 refresh 请求的非标准用例
            TokenRequest requestToSend = request;
            try {
                if (request != null && "refresh_token".equals(request.grantType)) {
                    String currentAccessToken = mStateManager.getCurrent().getAccessToken();
                    if (currentAccessToken != null && !currentAccessToken.isEmpty()) {
                        Map<String, String> additional = new HashMap<>(request.additionalParameters);
                        additional.put("access_token", currentAccessToken);

                        TokenRequest.Builder builder = new TokenRequest.Builder(
                                request.configuration,
                                request.clientId)
                                .setGrantType(request.grantType)
                                .setRefreshToken(request.refreshToken)
                                .setRedirectUri(request.redirectUri)
                                .setScope(request.scope)
                                .setCodeVerifier(request.codeVerifier)
                                .setNonce(request.nonce)
                                .setAuthorizationCode(request.authorizationCode)
                                .setAdditionalParameters(additional);

                        requestToSend = builder.build();
                        try {
                            Log.i(TAG, "performTokenRequest: modified refresh request=" + requestToSend.jsonSerializeString());
                        } catch (Exception e) {
                            Log.i(TAG, "performTokenRequest: 无法序列化修改后的请求", e);
                        }
                        Log.d(TAG, "已向 refresh 请求添加 access_token 参数（已掩码）: " + maskToken(currentAccessToken));
                    } else {
                        Log.w(TAG, "没有可用的当前 access token 用于作为 refresh 请求的额外参数");
                    }
                }
            } catch (Exception e) {
                Log.w(TAG, "尝试向 refresh 请求添加 access_token 时发生错误", e);
            }

            mAuthService.performTokenRequest(
                    requestToSend,
                    clientAuthentication,
                    callback);
        } catch (Exception e) {
            // 捕获 AuthorizationService API 抛出的同步异常
            Log.e(TAG, "performTokenRequest 抛出异常", e);
            runOnUiThread(() -> displayNotAuthorized("Token request failed synchronously: " + e.getMessage()));
        }
    }

    // 辅助函数：掩码输出 token，以避免在日志中泄露完整令牌
    private String maskToken(String token) {
        if (token == null) return "null";
        if (token.length() <= 10) return "****";
        return token.substring(0, 6) + "..." + token.substring(token.length() - 4);
    }

    @WorkerThread
    // 处理刷新 access token 的回调，更新应用内状态并返回 UI 线程刷新界面
    private void handleAccessTokenResponse(
            @Nullable TokenResponse tokenResponse,
            @Nullable AuthorizationException authException) {
        // 记录详细信息以便调试
        Log.i(TAG, "handleAccessTokenResponse: tokenResponse=" + tokenResponse + " authException=" + authException);
        if (tokenResponse != null) {
            Log.d(TAG, "accessToken=" + tokenResponse.accessToken
                    + " idToken=" + tokenResponse.idToken
                    + " refreshToken=" + tokenResponse.refreshToken
                    + " expiresIn=" + tokenResponse.accessTokenExpirationTime
                    + " scope=" + tokenResponse.scope);
        }
        if (authException != null) {
            String detailed = "Token response error: " + safeGetMessage(authException)
                    + "\nerror=" + authException.error
                    + "\nerrorDescription=" + authException.errorDescription
                    + "\nerrorUri=" + authException.errorUri
                    + "\ncode=" + authException.code;
            Log.e(TAG, detailed, authException);
        }
        mStateManager.updateAfterTokenResponse(tokenResponse, authException);
        runOnUiThread(this::displayAuthorized);
    }

    @WorkerThread
    // 处理授权码交换的回调；若成功则更新状态，否则显示错误并返回未授权界面
    private void handleCodeExchangeResponse(
            @Nullable TokenResponse tokenResponse,
            @Nullable AuthorizationException authException) {

        // 记录详细信息以便调试
        Log.i(TAG, "handleCodeExchangeResponse: tokenResponse=" + tokenResponse + " authException=" + authException);
        if (tokenResponse != null) {
            Log.d(TAG, "code exchange success: accessToken=" + tokenResponse.accessToken
                    + " idToken=" + tokenResponse.idToken
                    + " refreshToken=" + tokenResponse.refreshToken
                    + " scope=" + tokenResponse.scope
                    + " expiresAt=" + tokenResponse.accessTokenExpirationTime);
        }
        if (authException != null) {
            String detailed = "Authorization Code exchange failed: " + safeGetMessage(authException)
                    + "\nerror=" + authException.error
                    + "\nerrorDescription=" + authException.errorDescription
                    + "\nerrorUri=" + authException.errorUri
                    + "\ncode=" + authException.code;
            Log.e(TAG, detailed, authException);
            mStateManager.updateAfterTokenResponse(tokenResponse, authException);

            // WrongThread 推断对 lambda 可能错误，故使用注释抑制检查
            //noinspection WrongThread
            runOnUiThread(() -> displayNotAuthorized(detailed));
            return;
        }

        mStateManager.updateAfterTokenResponse(tokenResponse, authException);
        runOnUiThread(this::displayAuthorized);
    }

    /**
     * 示例：使用 AuthState.performActionWithFreshTokens 在获取 fresh tokens 后执行获取 userinfo
     * 如果需要刷新令牌，该回调会触发刷新并在完成后执行传入操作。
     */
    @MainThread
    private void fetchUserInfo() {
        displayLoading("Fetching user info");
        // Some non-standard servers require the current access_token to be supplied when
        // performing a refresh_token grant. Provide it as an additional parameter to
        // performActionWithFreshTokens so the internally created refresh request contains it.
        String currentAccessToken = mStateManager.getCurrent().getAccessToken();
        Map<String, String> additionalParams = new HashMap<>();
        if (currentAccessToken != null && !currentAccessToken.isEmpty()) {
            additionalParams.put("access_token", currentAccessToken);
            Log.d(TAG, "fetchUserInfo: will pass access_token as additional param (masked): " + maskToken(currentAccessToken));
        } else {
            Log.d(TAG, "fetchUserInfo: no current access token to pass as additional param");
        }

        // 使用 AuthState 的 performActionWithFreshTokens 来确保使用非过期的令牌执行后续请求
        mStateManager.getCurrent().performActionWithFreshTokens(mAuthService, additionalParams, (accessToken, idToken, ex) -> {
            fetchUserInfo(accessToken, idToken, ex);
        });
    }

    @MainThread
    private void fetchUserInfo(String accessToken, String idToken, AuthorizationException ex) {
        if (ex != null) {
            Log.e(TAG, "Token refresh failed when fetching user info");
            mUserInfoJson.set(null);
            runOnUiThread(this::displayAuthorized);
            return;
        }

        // 从当前 AuthState 中获取 discovery 文档用于解析 userinfo endpoint
        AuthorizationServiceDiscovery discovery =
                mStateManager.getCurrent()
                        .getAuthorizationServiceConfiguration()
                        .discoveryDoc;

        Uri userInfoEndpoint =
                    mConfiguration.getUserInfoEndpointUri() != null
                        ? Uri.parse(mConfiguration.getUserInfoEndpointUri().toString())
                        : Uri.parse(discovery.getUserinfoEndpoint().toString());

        mExecutor.submit(() -> {
            try {
                // 打开连接并设置 Authorization 头
                HttpURLConnection conn = mConfiguration.getConnectionBuilder().openConnection(
                        userInfoEndpoint);
                conn.setRequestProperty("Authorization", "Bearer " + accessToken);
                conn.setInstanceFollowRedirects(false);
                String response = Okio.buffer(Okio.source(conn.getInputStream()))
                        .readString(Charset.forName("UTF-8"));
                mUserInfoJson.set(new JSONObject(response));
            } catch (IOException ioEx) {
                Log.e(TAG, "Network error when querying userinfo endpoint", ioEx);
                showSnackbar("Fetching user info failed");
            } catch (JSONException jsonEx) {
                Log.e(TAG, "Failed to parse userinfo response");
                showSnackbar("Failed to parse user info");
            }

            runOnUiThread(this::displayAuthorized);
        });
    }

    // 显示用户取消登出操作的提示
    private void displayEndSessionCancelled() {
        Snackbar.make(findViewById(R.id.coordinator),
            "Sign out canceled",
            Snackbar.LENGTH_SHORT)
                .show();
    }

    @MainThread
    private void showSnackbar(String message) {
        Snackbar.make(findViewById(R.id.coordinator),
                message,
                Snackbar.LENGTH_SHORT)
                .show();
    }

    @MainThread
    // 发起结束会话（登出）流程：如果 IDP 支持 end_session endpoint，会跳转到浏览器执行登出；否则在本地执行 signOut
    private void endSession() {
        AuthState currentState = mStateManager.getCurrent();
        AuthorizationServiceConfiguration config =
                currentState.getAuthorizationServiceConfiguration();
        if (config.endSessionEndpoint != null) {
            Intent endSessionIntent = mAuthService.getEndSessionRequestIntent(
                    new EndSessionRequest.Builder(config)
                        .setIdTokenHint(currentState.getIdToken())
                        .setPostLogoutRedirectUri(mConfiguration.getEndSessionRedirectUri())
                        .build());
            endSessionLauncher.launch(endSessionIntent);
        } else {
            signOut();
        }
    }

    @MainThread
    // 本地登出：清空授权/令牌状态，但保留配置与动态注册信息
    private void signOut() {
        AuthState currentState = mStateManager.getCurrent();
        AuthState clearedState =
                new AuthState(currentState.getAuthorizationServiceConfiguration());
        if (currentState.getLastRegistrationResponse() != null) {
            clearedState.update(currentState.getLastRegistrationResponse());
        }
        mStateManager.replace(clearedState);

        Intent mainIntent = new Intent(this, LoginActivity.class);
        mainIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(mainIntent);
        finish();
    }

    // 辅助：安全获取 AuthorizationException 的消息，避免返回 null
    private static String safeGetMessage(@Nullable AuthorizationException ex) {
        if (ex == null) return "null";
        String m = ex.getMessage();
        return m != null ? m : ex.toString();
    }

    // 将 Intent 的所有 extras 输出到日志，方便调试来自浏览器的重定向响应
    private void logIntentExtras(@Nullable Intent intent) {
        if (intent == null) {
            Log.d(TAG, "logIntentExtras: intent is null");
            return;
        }
        Bundle extras = intent.getExtras();
        if (extras == null || extras.isEmpty()) {
            Log.d(TAG, "logIntentExtras: no extras on intent");
            return;
        }
        StringBuilder sb = new StringBuilder();
        sb.append("Intent extras:\n");
        for (String key : extras.keySet()) {
            Object val = extras.getString(key);
            sb.append("  ").append(key).append(" = ").append(String.valueOf(val)).append("\n");
        }
        Log.d(TAG, sb.toString());
    }
}
